package com.csx.springcloud.governance.sentinel.defineResource;

import com.alibaba.csp.sentinel.*;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author cuisongxu
 * @date 2023/2/3 10:55
 */
@RestController
public class SentinelDefineResource {

    @Autowired
    private AsyncService asyncService;

    /**
     * try catch - finally 定义资源
     */
    public void modeA() {

        Entry entry = null;
        try {
            // 资源名可使用任意有业务语义的字符串，注意数目不能太多(超过1K)的资源名字符串请用参数
            entry = SphU.entry("modeA-resourceName");
            // TODO 这里填写被保护的逻辑
            // do something
        } catch (BlockException e) {
            // 资源 被限流 或者 被熔断降级 ,进行相应的处理
        } catch (Exception e) {
            // 在这里记录业务异常
            Tracer.traceEntry(e, entry);
        } finally {
            // 务必保证 exit, 务必保证每个entry 与 exit 配对, 退出顺序应该为: entry1 -> entry2 -> exit2 -> exit1
            if(entry != null) {
                entry.exit();
            }
        }
    }

    /**
     * try - catch - finally 热点参数
     * SphU.entry() 的参数描述:
     *  entryType  EntryTpe   资源调用的流量类型。 入口流量(EntryType.IN) 出口流量 (EntryType.OUT)，注意系统规则只对 IN生效.  ，默认为 EntryType.OUT
     *  count        int      本地资源调用请求的token数目   默认为1
     *  args        Object[]  传入的参数，用于热点参数限流    无
     */
    public void modeA2() {

        Entry entry = null;
        try {
            // 若需要配置例外项，则传入的参数只支持基本类型
            // EntryType 代表流量类型，其中系统规则只对 IN 类型的埋点生效, 默认为 EntryType.OUT
            // count 大多数情况下都填1，代表统一为一次调用
            entry = SphU.entry("modeA2-resourceName", EntryType.IN, 1, "paramA", "paramB");

        } catch (BlockException e) {
            // Handle request rejection
        } catch (Exception e) {
            // 记录业务异常
            Tracer.traceEntry(e, entry);
        } finally {
            // 注意，exit的时候也一定要带上对应的参数，否则可能会有统计错误
            if(entry != null) {
                entry.exit(1, "paramA", "paramB");
            }
        }
    }

    /**
     * if - else 定义资源
     */
    public void modeB() {
        // 资源名可以使用任意有业务语义的字符串
        if(SphO.entry("modeB-resourceName")) {

            try {
                // TODO 编写被保护的逻辑
            } finally {
                // SphO.entry() 需要与 SphO.exit() 方法成对出现，匹配调用，位置正确，否则会导致调用链记录异常
                // 抛出 ErrorEntryFreeException 异常
                SphO.exit();
            }
        } else {

            // 资源访问阻止，被限流或被降级
            // 进行相应的处理操作
        }
    }

    /**
     * 注解方式定义资源
     * 这里是原本的业务方法
     */
    @SentinelResource(blockHandler = "blockHandlerForGetUser")
    public void modeC() {
        throw new RuntimeException("getUserById command failed");
    }

    /**
     * blockHandler 函数, 原方法调用被限流/降级/系统保护的时候调用
     * @param id
     * @param e
     * @return
     */
    public User blockHandlerForGetUser(String id,BlockException e) {
        return new User("admin");
    }


    @Data
    @AllArgsConstructor
     class User {
        String name;
    }

    /**
     * 异步调用支持:
     *  Sentinel 支持异步调用链路的统计。在异步调用中，需要通过 SphU.asyncEntry(xxx) 方法定义资源，并
     *  通常需要在异步的回调中调用 exit 方法。
     */
    public void modeD() {
        AsyncEntry asyncEntry = null;
        try {
            asyncEntry = SphU.asyncEntry("modeD-resourceName");
            // 异步调用
            asyncService.doSomethingAsync();
        } catch (BlockException e) {
            System.out.println("系统繁忙，请稍后!");
        } finally {
            if(asyncEntry != null) {
                asyncEntry.exit();
            }
        }
    }

    @Service
    static class AsyncService {

        @Async
        public void doSomethingAsync() {
            System.out.println("async start...");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("async end...");
        }
    }
}